#ifndef LUA_AV_H
#define LUA_AV_H 1

#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavformat/avformat.h>

#include "lua.h"
#include "lauxlib.h"

struct lua_av_object_s;

typedef struct lua_av_object_link_s {
	struct lua_av_object_s * child;
	struct lua_av_object_link_s * next;
} lua_av_object_link;

typedef struct lua_av_object_s {
	int type_id;
	void * data;
	struct lua_av_object_s * parent;
	lua_av_object_link * children;
	int lua_ref;
	void (*destroy)(void *);
} lua_av_object;

typedef struct lua_av_type_s {
	int type_id;
	void (*default_destroy)(void *);
	const char * metatable;
	const struct luaL_reg * methods;
} lua_av_type;

extern void lua_object_attach(lua_State *, lua_av_object *, int);
extern void lua_object_detach(lua_State *, lua_av_object *);

#define DEFINE_PUSH_FUNCTION(NAME, TYPE) \
	extern lua_av_object * lua_push_##NAME(lua_State * L, TYPE * object) { \
		fprintf(stderr, "PUSH %p %s %s\n", object, #NAME, #TYPE); \
		lua_av_object * o = (lua_av_object *) lua_newuserdata(L, sizeof(lua_av_object)); \
		o->type_id = lua_type_##NAME.type_id; \
		o->data = object; \
		o->parent = 0; \
		o->children = 0; \
		o->lua_ref = LUA_NOREF; \
		o->destroy = lua_type_##NAME.default_destroy; \
		luaL_getmetatable (L, lua_type_##NAME.metatable); \
		lua_setmetatable (L, -2); \
		return o; \
	}

#define DEFINE_CHECK_FUNCTION(NAME, TYPE) \
	extern lua_av_object * lua_check_##NAME(lua_State * L, int index, TYPE ** pdata) { \
		lua_av_object * o = (lua_av_object *) lua_touserdata(L, index); \
		if (o->type_id != lua_type_##NAME.type_id) { \
			luaL_error(L, "Incompatible type for parameter %d, expected %s", index, lua_type_##NAME.metatable); \
			return 0; \
		} \
		if (!o->data) { \
			luaL_error(L, "Trying to access a deleted object"); \
			return 0; \
		} \
		if (pdata) { \
			*pdata = o->data; \
		} \
		return o; \
	}

#define DEFINE_DESTROY_FUNCTION(NAME, TYPE) \
	static int lua_destroy_##NAME(lua_State * L) { \
		TYPE * object; \
		lua_av_object * o = lua_check_##NAME(L, 1, &object); \
		fprintf(stderr, "DESTROY %p %d %s %s\n", object, o->type_id, #NAME, #TYPE); \
		if (o->parent) { \
			luaL_error(L, "lua object in invalid state, destroy while attached"); \
			return 0; \
		} \
		while (o->children) { \
			lua_object_detach(L, o->children->child); \
		} \
		if (o->lua_ref != LUA_NOREF) { \
			luaL_error(L, "lua object in invalid state, destroy while still ref'd"); \
			return 0; \
		} \
		if (o->destroy && object) { \
			o->destroy(object); \
			o->destroy = 0; \
			o->data = 0; \
		} \
		return 0; \
	}

#define DEFINE_REGISTER_FUNCTION(name, type) \
	extern int lua_register_##name(lua_State * L) { \
		lua_create_type(L, lua_type_##name.metatable, \
			lua_destroy_##name, lua_index_##name, lua_newindex_##name); \
		return 0; \
	}

#define DECLARE_TYPE(NAME, TYPE) \
	extern lua_av_object * lua_push_##NAME(lua_State *, TYPE *); \
	extern lua_av_object * lua_check_##NAME(lua_State *, int, TYPE **); \
	extern int lua_register_##NAME(lua_State *);


#define DEFINE_TYPE_STRUCT(NAME, TYPE, ID, DESTROY, METHODS) \
	static const lua_av_type lua_type_##NAME = { \
		ID, \
		DESTROY, \
		#TYPE, \
		METHODS, \
	};

#define DEFINE_INDEX_HEADER(NAME, TYPE) \
	static int lua_index_##NAME(lua_State * L) { \
		TYPE * object; \
		lua_av_object * lua_object; \
		const char * key; \
		const struct luaL_reg * method; \
		\
		lua_object = lua_check_##NAME(L, 1, &object); \
		key = luaL_checkstring(L, 2); \
		fprintf(stderr, "INDEX %p %d %s %s %s\n", object, lua_object->type_id, #NAME, #TYPE, key); \
		method = lua_type_##NAME.methods; \
		lua_object = lua_object; \
		if (method) { \
			while (method->name) { \
				if (strcmp(method->name, key) == 0) { \
					lua_pushcfunction(L, method->func); \
					return 1; \
				} \
				method++; \
			} \
		}
#define HANDLE_INDEX_ENTRY(NAME, PUSH_METHOD) \
		if (strcmp(key, #NAME) == 0) { \
			fprintf(stderr, "HANDLE_INDEX_ENTRY %s %p\n", #NAME, object); \
			PUSH_METHOD(L, object-> NAME); \
			return 1; \
		}
#define DEFINE_INDEX_TRAILER() \
		luaL_error(L, "Invalid key '%s' requested", key); \
		return 0; \
	}

#define DEFINE_NEWINDEX_HEADER(NAME, TYPE) \
	static int lua_newindex_##NAME(lua_State * L) { \
		TYPE * object; \
		lua_av_object * lua_object = lua_check_##NAME(L, 1, &object); \
		const char * key = luaL_checkstring(L, 2); \
		fprintf(stderr, "NEWINDEX %d %s %s %s\n", lua_object->type_id, #NAME, #TYPE, key); \
		lua_object = lua_object;
#define HANDLE_NEWINDEX_ENTRY(NAME, CHECK_METHOD) \
		if (strcmp(key, #NAME) == 0) { \
			object-> NAME = CHECK_METHOD(L, 3); \
			return 0; \
		}
#define HANDLE_NEWINDEX_OBJECT(NAME, NAME2) \
		if (strcmp(key, #NAME) == 0) { \
			lua_check_##NAME2(L, 3, 0); \
			lua_object_attach(L, lua_object, 3); \
			return 0; \
		}

#define DEFINE_NEWINDEX_TRAILER() \
		luaL_error(L, "Invalid key '%s' requested", key); \
		return 0; \
	}

#define DEFINE_OBJECT_FUNCTIONS(NAME, TYPE) \
	DEFINE_PUSH_FUNCTION(NAME, TYPE) \
	DEFINE_CHECK_FUNCTION(NAME, TYPE) \
	DEFINE_DESTROY_FUNCTION(NAME, TYPE) \
	DEFINE_REGISTER_FUNCTION(NAME, TYPE)

extern int lua_create_type(lua_State *, char const *, \
	lua_CFunction, lua_CFunction, lua_CFunction);
extern void * lua_get_type_data(lua_State *, int);

/* CONSTANTS */
typedef struct lua_constant_s {
	const char * name;
	int value;
} lua_constant;

extern int lua_register_av_constants(lua_State *);

extern void lua_push_codec_id(lua_State *, enum CodecID);
extern enum CodecID lua_check_codec_id(lua_State *, int);

extern void lua_push_codec_type(lua_State *, enum CodecType);
extern enum CodecType lua_check_codec_type(lua_State *, int);

extern void lua_push_sample_format(lua_State *, enum SampleFormat);
extern enum SampleFormat lua_check_sample_format(lua_State *, int);

extern void lua_push_pixel_format(lua_State *, enum PixelFormat);
extern enum PixelFormat lua_check_pixel_format(lua_State *, int);

extern void lua_push_me_method(lua_State *, enum Motion_Est_ID);
extern enum Motion_Est_ID lua_check_me_method(lua_State *, int);

/* FLAG objects */
extern int lua_register_av_flag_type(lua_State *L);
void lua_push_flag(lua_State * L, int value, lua_constant * constants);

#define LUA_OBJECT_FORMAT_CONTEXT_ID 1
#define LUA_OBJECT_OUTPUT_FORMAT_ID 2
#define LUA_OBJECT_FRAME_ID 3
#define LUA_OBJECT_STREAM_ID 4
#define LUA_OBJECT_CODEC_CONTEXT_ID 5
#define LUA_OBJECT_CODEC_ID 6
#define LUA_OBJECT_PACKET_ID 7
#define LUA_OBJECT_SWS_CONTEXT_ID 8

DECLARE_TYPE(format_context, AVFormatContext)
DECLARE_TYPE(output_format, AVOutputFormat)
DECLARE_TYPE(frame, AVFrame)
DECLARE_TYPE(stream, AVStream)
DECLARE_TYPE(codec_context, AVCodecContext)
DECLARE_TYPE(codec, AVCodec)
DECLARE_TYPE(packet, AVPacket)
DECLARE_TYPE(sws_context, struct SwsContext)

extern int lua_format_context_open(lua_State *);
extern int lua_format_context_new(lua_State *);
extern int lua_output_format_guess(lua_State *);
extern int lua_new_packet(lua_State *);
extern int lua_new_frame(lua_State *);
extern int lua_new_codec_context(lua_State *);
extern int lua_find_decoder(lua_State *);
extern int lua_find_encoder(lua_State *);
extern int lua_sws_context_new(lua_State *);

#endif
